/*
* Copyright (c) 2010 Michael Collins
*
* This file is part of TerraFirma.
*
* TerraFirma is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* TerraFirma is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with TerraFirma.  If not, see <http://www.gnu.org/licenses/>.
*/

/* Signed fixed-point numeric format with 16.16 bits of precision (1.5E-5 resolution) */
#ifndef FIXPOINT_H
#define FIXPOINT_H
#include <stdint.h>

#define UNIT 0x00010000
#define RESOLUTION 0x00000001
#define RADIX 16

namespace fixedpoint
{
    class fixpoint
    {
    public:
        enum r{RAW}; //Used to initialize the guts directly
        //ctors
        fixpoint(): g(0) {}
        fixpoint(const float a): g((int32_t) (a * UNIT)) {}
        fixpoint(const double a): g((int32_t) (a * UNIT)) {}
        fixpoint(const int a): g((int32_t) (a * UNIT)) {}
        fixpoint(const long a): g((int32_t) (a * UNIT)) {}
        fixpoint(const long double a): g((int32_t) (a * UNIT)) {}
        fixpoint(const unsigned a): g((int32_t) (a * UNIT)) {}
        fixpoint(r, int32_t a): g(a) {} //Raw explicit construction

        //Raw copy assignment
        fixpoint& raw_copy(int32_t a) {g = a; return *this;}

        //modifying assignment
        fixpoint& operator +=(fixpoint a) {g += a.g; return *this;}
        fixpoint& operator -=(fixpoint a) {g -= a.g; return *this;}
        fixpoint& operator ^=(fixpoint a) {g ^= a.g; return *this;}
        fixpoint& operator ^=(int32_t a)  {g ^= a; return *this;}
        fixpoint& operator &=(fixpoint a) {g &= a.g; return *this;}
        fixpoint& operator &=(int32_t a)  {g &= a; return *this;}
        fixpoint& operator |=(fixpoint a) {g |= a.g; return *this;}
        fixpoint& operator |=(int32_t a)  {g |= a; return *this;}
        fixpoint& operator <<=(uint32_t a)  {g <<= a; return *this;}
        fixpoint& operator >>=(uint32_t a)  {g >>= a; return *this;}
        fixpoint& operator /=(fixpoint a) {return (operator=(operator/(a)));}
        fixpoint& operator %=(fixpoint a) {return (operator=(operator%(a)));}
        fixpoint& operator *=(fixpoint a) {return (operator=(operator*(a)));}

        //Binary operators
        fixpoint operator +(fixpoint a) const {return fixpoint(RAW, g + a.g);}
        fixpoint operator -(fixpoint a) const {return fixpoint(RAW, g - a.g);}
        fixpoint operator ^(fixpoint a) const {return fixpoint(RAW, g ^ a.g);}
        fixpoint operator ^(int32_t a) const  {return fixpoint(RAW, g ^ a);}
        fixpoint operator &(fixpoint a) const {return fixpoint(RAW, g & a.g);}
        fixpoint operator &(int32_t a) const  {return fixpoint(RAW, g & a);}
        fixpoint operator |(fixpoint a) const {return fixpoint(RAW, g | a.g);}
        fixpoint operator |(int32_t a) const  {return fixpoint(RAW, g | a);}
        fixpoint operator <<(uint32_t a) const  {return fixpoint(RAW, g << a);}
        fixpoint operator >>(uint32_t a) const  {return fixpoint(RAW, g >> a);}
        fixpoint operator /(fixpoint a) const
        {
            int64_t numerator= (g << RADIX);
            return fixpoint(RAW, numerator / a.g);
        }
        fixpoint operator %(fixpoint a) const
        {
            int64_t numerator= (g << RADIX);
            return fixpoint(RAW, numerator % a.g);
        }
        fixpoint operator *(fixpoint a) const
        {
            //This is special and needs to be written in assembly because it requires all 64 bits of result
            //NOTE: This is written in AT&T syntax for GNU assembler. Perhaps make more portable in the future
            uint32_t upper, lower;
            asm ("movl %2, %%eax\n\t"
                 "imul %3"
                 :"=d"(upper), "=a"(lower) //output list
                 :"r"(g), "r"(a.g)
                 : //No clobbers
                 );
            return fixpoint(RAW, (upper << 16) | (lower >> 16));
        }

        //Testing operators
        bool operator ==(fixpoint a) const {return (g == a.g);}
        bool operator <=(fixpoint a) const {return (g <= a.g);}
        bool operator >=(fixpoint a) const {return (g >= a.g);}
        bool operator !=(fixpoint a) const {return (g != a.g);}
        bool operator < (fixpoint a) const {return (g < a.g);}
        bool operator > (fixpoint a) const {return (g > a.g);}

        //Unary operators
        fixpoint operator +() const {return fixpoint(RAW, +g);}
        fixpoint operator -() const {return fixpoint(RAW, -g);}
        fixpoint operator ~() const {return fixpoint(RAW, ~g);}

        //Type Casting
        operator float() const {return ((float)g / UNIT);}
        operator double() const {return ((double)g / UNIT);}
        operator int() const {return (g / UNIT);}
        operator long() const {return (g / UNIT);}
        int32_t raw() const { return g; }

        //Special function for multiplying by the ratio of 2 other fixedpoints
        fixpoint ratio(fixpoint a, fixpoint b)
        {
            int32_t quotient;
            asm("movl %1 %%eax\n\t"
                "imul %2\n\t"
                "idiv %3"
                :"=a" (quotient)
                :"mr" (g), "mr" (a.g), "mr" (b.g)
                :
                );
            return fixpoint(RAW, quotient);
        }

    protected:
    private:
    int32_t g;
    };

    //Some useful constants
    /*
    const fixpoint PI(); //pi
    const fixpoint PI_2(); //pi / 2
    const fixpoint PI_INV(); // 1 / pi
    const fixpoint PI_2_INV(); // 2 / pi
    const fixpoint E(); //e (2.718...)
    */
    const fixpoint F_E(fixpoint::RAW, 0x0002b7e1);
    const fixpoint F_LOG2E(fixpoint::RAW, 0x00017154);
    const fixpoint F_LOG10E(fixpoint::RAW, 0x00006f2d);
    const fixpoint F_LN2(fixpoint::RAW, 0x0000b172);
    const fixpoint F_LN10(fixpoint::RAW, 0x00024d76);
    const fixpoint F_PI(fixpoint::RAW, 0x0003243f);
    const fixpoint F_PI_2(fixpoint::RAW, 0x0001921f);
    const fixpoint F_PI_4(fixpoint::RAW, 0x0000c90f);
    const fixpoint F_1_PI(fixpoint::RAW, 0x0000517c);
    const fixpoint F_2_PI(fixpoint::RAW, 0x0000a2f9);
    const fixpoint F_2_SQRTPI(fixpoint::RAW, 0x000120dd);
    const fixpoint F_SQRT2(fixpoint::RAW, 0x00016a09);
    const fixpoint F_SQRT1_2(fixpoint::RAW, 0x0000b504);
    const fixpoint ONE(fixpoint::RAW, UNIT);

    //Math function declarations
    fixpoint sin(fixpoint a);
    fixpoint cos(fixpoint a);
    fixpoint tan(fixpoint a);
    void sincos(fixpoint a, fixpoint& sin, fixpoint& cos);
    fixpoint asin(fixpoint a);
    fixpoint acos(fixpoint a);
    fixpoint atan(fixpoint a);
    fixpoint atan2(fixpoint y, fixpoint x);
    fixpoint sqrt(fixpoint a);
    fixpoint invsqrt(fixpoint a);

}

#endif // FIXPOINT_H
